package jastgen.generator;

import static jastgen.generator.Helper.*;
import java.io.PrintWriter;
import java.util.List;
import jastgen.nodes.*;

public class ContextBodyGenerator implements BodyGenerator {

	private final List<ClassDef> classDefs;

	ContextBodyGenerator(
			final List<ClassDef> classDefs)
	{
		this.classDefs = classDefs;
	}

	@Override
	public void generate(final PrintWriter out) {

		out.println("public final class Context {");
		out.println();

		printPurge(out);
		printFactory(out);
		printIntern(out);

		out.println("}");
	}

	private void printPurge(final PrintWriter out) {

		// 最初に定義されたクラスをrootノードの型とする
		final String cn = classDefs.get(0).getInterfaceOrClassBaseName();

		out.println("	/**");
		out.println("	 * After the call of this method, this Context has only all nodes reachable from argument node.");
		out.println("	 * @param root Root node of tree corresponding to this Context.");
		out.println("	 */");
		out.println("	public void purgeUnusedNodes(");
		out.println("			final " + cn + " root)");
		out.println("	{");
		out.println("		final NodesCollector collector = new NodesCollector();");
		out.println("		root.visit(collector);");
		out.println("		assign(collector.context);");
		out.println("	}");
		out.println();
		out.println("	private void assign(final Context rhs) {");
		out.println();

		for(final ClassDef c : classDefs) {
			final String n = c.getBaseName();
			if(!c.hasNormalFields()) {
				if(!c.hasFieldWithBody())
					continue;
			}
			out.println("		map" + n + " = rhs.map" + n + ";");
		}

		out.println("	}");
	}

	private void printFactory(final PrintWriter out) {

		for(final ClassDef c : classDefs)
			printFactory(out, c);
	}

	private void printFactory(
			final PrintWriter out,
			final ClassDef node)
	{
		final String n = node.getBaseName();
		final String ifn = node.getInterfaceOrClassName();

		out.println();
		out.println("	/**");
		out.println("	 * Factory method of '" + n + "' node.");
		// out.println("	 * @param ${FIELD_NAME} Element of return value.");
		out.println("	 * @return Instance of '" + n + "' class.");
		out.println("	 */");

		out.print("	public ");
		out.print(node.getTypeParamsString());
		out.print(ifn + " get" + n + "(");

		printContextFactoryParams(out, node);

		out.println(")");
		out.println("	{");
		out.println("		return " + n + ".get(");
		out.print("				this");

		printContextFactoryArgs(out, node, false);

		out.println(");");
		out.println("	}");
	}

	private void printIntern(final PrintWriter out) {

		boolean use = false;
		for(final ClassDef c : classDefs) {
			// フィールドが存在しなければsingletonなのでmapは不要
			if(!c.hasNormalFields()) {
				if(!c.hasFieldWithBody())
					continue;
				// bodyを持つ場合、ノードクラスを持つ可能性がある。
				// 規約により内容 (equals, hashCode, compareTo) は毎回一定だが、
				// メモリ上ではContextごとに別のオブジェクトである可能性がある。
				printIntern(out, c, true);
			} else {
				printIntern(out, c, false);
				use = true;
			}
		}

		if(use)
			printStaticIntern(out);
	}

	private void printIntern(
			final PrintWriter out,
			final ClassDef c,
			boolean isAllFieldsHasBody)
	{
		final String n  = c.getName();
		final String bn = c.getBaseName();
		final String ps = c.getTypeParamsString();

		out.println();
		if(isAllFieldsHasBody) {
			out.println("	private " + bn + " map" + bn + ";");
		} else {
			out.println("	private Map<" + bn + ", " + bn + "> map" + bn + " =");
			out.println("		new HashMap<" + bn + ", " + bn + ">();");
		}
		out.println("	/**");
		out.println("	 * Ignore this method, or program will be broken.");
		out.println("	 * (Used by generated classes only.)");
		out.println("	 */");
		out.println("	public " + ps + n + " intern(");
		out.println("			final " + n + " node)");
		out.println("	{");
		if(isAllFieldsHasBody) {
			out.println("		if(map" + bn + " == null)");
			out.println("			map" + bn + " = node;");
			out.println("		return map" + bn + ";");
		} else {
			out.println("		return intern(map" + bn + ", node);");
		}
		out.println("	}");
	}

	private void printStaticIntern(
			final PrintWriter out)
	{
		out.println();
		out.println("	private static <T> T intern(");
		out.println("			final Map<T, T> m,");
		out.println("			final T e)");
		out.println("	{");
		out.println("		if(m.containsKey(e))");
		out.println("			return m.get(e);");
		out.println("		m.put(e, e);");
		out.println("		return e;");
		out.println("	}");
	}
}
